#include <QtGui>

#include <iostream>

#include "invader_frame.h"
#include "invader_army.h"
#include "invader_bullet.h"
#include "invader_label.h"
#include "invader.h"
#include "tank.h"
#include "tank_bullet.h"
#include "media.h"
#include "bunker.h"

InvaderFrame::InvaderFrame (QWidget *parent)
  : QFrame (parent)
{
  _army = new InvaderArmy (this);
  _army->move (0, 0);
  _army->setMouseTracking (true);
  connect (_army, SIGNAL (frameChanged ()),
           this, SLOT (armyFrameChanged ()));
  connect (_army, SIGNAL (invaderKilled (int)),
           this, SLOT (armyInvaderKilled (int)));
  connect (_army, SIGNAL (invaderShoots (QPoint)),
           this, SLOT (armyShoots (QPoint)));
  _armyMovement = 3;
  _armyHeight = 30;

  _lives = 3;

  _tank = new Tank (MEDIA_DIR TANK_IMG, this);
  _tank->setFocusPolicy (Qt::NoFocus);
  _desiredTankPosition.setX (width () / 2);
  _desiredTankPosition.setY (height () - _tank->height ());
  _tank->move (_desiredTankPosition);
  _moveTankDPos = 0;
  setMouseTracking (true);

  _tankBullet = new TankBullet (this);
  _tankBullet->setVisible (false);
  _fireBullet = false;

  _animationTimer = new QTimer (this);
  _animationTimer->setInterval (33);
  connect (_animationTimer, SIGNAL (timeout ()),
           this, SLOT (animationTimerTimeout ()));

  for (int i = 0; i < 4; ++i) {
    Bunker *b = new Bunker (MEDIA_DIR BUNKER_SQUARE_GIF,
                            MEDIA_DIR BUNKER_NE_GIF,
                            MEDIA_DIR BUNKER_SE_GIF,
                            MEDIA_DIR BUNKER_SW_GIF,
                            MEDIA_DIR BUNKER_NW_GIF,
                            this);
    QSize s = b->sizeHint ();
    _bunkerWidth = s.width ();
    int x = (i + 1) * (530 - 4 * _bunkerWidth) / 5 + i * _bunkerWidth;
    b->setGeometry (x, 240, s.width (), s.height ());
    b->setMouseTracking (true);
    _bunkers.insert (std::pair<int, Bunker *> (x, b));
  }

  _score = 0;

  _pauseLabel = new InvaderLabel ("paused", this);
  _pauseLabel->setVisible (false);
  _pauseBlinkTimer = new QTimer (this);
  _pauseBlinkTimer->setInterval (300);
  connect (_pauseBlinkTimer, SIGNAL (timeout ()),
           this, SLOT (pauseBlinkTimerTimeout ()));

  _mysteryShip = new Tank (MEDIA_DIR MYSTERY_BMP, this);
  _mysteryShip->setMouseTracking (true);
  _mysteryShip->setVisible (false);
}

void InvaderFrame::start ()
{
  _animationTimer->start ();
  _army->start ();
}

void InvaderFrame::stop ()
{
  _animationTimer->stop ();
  _army->stop ();
}

void InvaderFrame::pause ()
{
  if (_animationTimer->isActive ()) {
    stop ();
    _pauseLabel->setVisible (true);
    _pauseBlinkTimer->start ();
  }
  else {
    start ();
    _pauseLabel->setVisible (false);
    _pauseBlinkTimer->stop ();
  }
}

void InvaderFrame::reset (bool demoMode)
{
  _demoMode = demoMode;

  _desiredTankPosition.setX (width () / 2);
  _desiredTankPosition.setY (height () - _tank->height ());
  _moveTankDPos = 0;
  _tank->move (width () / 2, height () - _tank->height ());
  _fireBullet = false;

  _lives = 3;
  emit livesChanged (_lives);

  _score = 0;
  emit scoreChanged (_score);

  _tankBullet->setVisible (false);

  std::list<InvaderBullet *>::iterator beg = _invaderBullets.begin ();
  std::list<InvaderBullet *>::iterator end = _invaderBullets.end ();

  for (; beg != end; ++beg)
    removeInvaderBullet (beg);

  _army->reset ();
  _armyHeight = 30;
  _armyMovement = 3;
  _army->move (0, _armyHeight);

  std::multimap<int, Bunker *>::iterator bbeg = _bunkers.begin ();
  std::multimap<int, Bunker *>::iterator bend = _bunkers.end ();

  for (; bbeg != bend; ++bbeg)
    bbeg->second->reset ();
}

void InvaderFrame::animationTimerTimeout ()
{
  if (_demoMode) {
    int y;
    int xoff;
    if (_armyMovement < 0)
      xoff = _army->getMinX (&y) + 4;
    else
      xoff = _army->getMaxX (&y) - 4;
    y = _tank->y () - _army->y () - y;

    int lead = 33 / 8 * y * _armyMovement / _army->getAnimationInterval ();

    _desiredTankPosition.setX (_army->x () + xoff + lead);
  }
  else
    _desiredTankPosition.setX (_desiredTankPosition.x () + _moveTankDPos);

  if (_mysteryShip->isHidden () && qrand () % 3000 < 10) {
    if (qrand () % 2 == 0) {
      _mysteryShip->move (-_mysteryShip->width (), 0);
      _mysteryShipMovement = 3;
    }
    else {
      _mysteryShip->move (width (), 0);
      _mysteryShipMovement = -3;
    }

    _mysteryShip->setVisible (true);
  }

  if (_desiredTankPosition.x () <= _tank->width () / 2)
    _desiredTankPosition.setX (_tank->width () / 2);
  if (_desiredTankPosition.x () > width () - _tank->width () / 2)
    _desiredTankPosition.setX (width () - _tank->width () / 2);

  if (_desiredTankPosition.x () - _tank->x () - _tank->width () / 2 <= -4
      && _tank->x () - 4 >= 0)
    _tank->move (_tank->x () - 4, _tank->y ());
  else if (_desiredTankPosition.x () - _tank->x () - _tank->width () / 2 >= 4
           && _tank->x () + 4 < width ())
    _tank->move (_tank->x () + 4, _tank->y ());

  if (_demoMode) {
    QRect bulletTraj = QRect (_tank->x () + _tank->width () / 2, 0,
                              1, height ());
    QRect armyArea = QRect (_army->x () + _army->getMinX (), _army->y (),
                            _army->getMaxX () - _army->getMinX (), 1);

    std::multimap<int, Bunker *>::iterator beg = _bunkers.lower_bound (
      bulletTraj.x () - _bunkerWidth);
    std::multimap<int, Bunker *>::iterator end = _bunkers.upper_bound (
      bulletTraj.x () + _bunkerWidth);

    _fireBullet = true;
    for (; beg != end; ++beg) {
      if (bulletTraj.intersects (beg->second->geometry ()))
        _fireBullet = false;
    }
  }

  if (_tankBullet->isVisible ()) {
    if (_tankBullet->y () - 8 < 0) {
      _tankBullet->setVisible (false);
    }
    else {
      _tankBullet->move (_tankBullet->x (), _tankBullet->y () - 8);
      const QRect &armyGeo = _army->geometry ();
      QRect bulletGeo = _tankBullet->geometry ();

      if (armyGeo.intersects (bulletGeo))
        _army->tryHitInvader (bulletGeo.translated (-armyGeo.x (),
                                                    -armyGeo.y ()));

      std::multimap<int, Bunker *>::iterator beg = _bunkers.lower_bound (
        bulletGeo.x () - _bunkerWidth);
      std::multimap<int, Bunker *>::iterator end = _bunkers.upper_bound (
        bulletGeo.x () + _bunkerWidth);

      for (; beg != end; ++beg) {
        const QRect &bunkerGeo = beg->second->geometry ();

        if (bunkerGeo.intersects (bulletGeo)) {
          if (beg->second->tryHit (bulletGeo.translated (-bunkerGeo.x (),
                                                         -bunkerGeo.y ()))) {
            _tankBullet->setVisible (false);
            break;
          }
        }
      }

      if (_mysteryShip->isVisible ()
          && bulletGeo.intersects (_mysteryShip->geometry ())) {
        _tankBullet->setVisible (false);
        _mysteryShip->setVisible (false);
        _score += 200;
        emit scoreChanged (_score);
      }
    }
  }
  else {
    if (_fireBullet) {
      _tankBullet->move (_tank->x () + _tank->width () / 2 - 1,
                         _tank->y () - _tankBullet->height ());
      _tankBullet->setVisible (true);
    }
  }

  std::list<InvaderBullet *>::iterator beg = _invaderBullets.begin ();
  std::list<InvaderBullet *>::iterator end = _invaderBullets.end ();

  for (; beg != end; ++beg) {
    QRect bulletGeo = (*beg)->geometry ();

    if (bulletGeo.y () + 8 >= height ()) {
      removeInvaderBullet (beg);
    }
    else {
      (*beg)->move (bulletGeo.x (), bulletGeo.y () + 8);
      (*beg)->advanceFrame ();

      std::multimap<int, Bunker *>::iterator bbeg = _bunkers.lower_bound (
        bulletGeo.x () - _bunkerWidth);
      std::multimap<int, Bunker *>::iterator bend = _bunkers.upper_bound (
        bulletGeo.x () + _bunkerWidth);

      for (; bbeg != bend; ++bbeg) {
        const QRect &bunkerGeo = bbeg->second->geometry ();

        if (bunkerGeo.intersects (bulletGeo)) {
          if (bbeg->second->tryHit (bulletGeo.translated (-bunkerGeo.x (),
                                                          -bunkerGeo.y ()))) {
            removeInvaderBullet (beg);
            break;
          }
        }
      }

      if (_tank->geometry ().intersects (bulletGeo)) {
        removeInvaderBullet (beg);
        emit livesChanged (--_lives);
        stop ();
        if (_lives == 0) {
          if (_demoMode)
            emit endDemoMode ();
          else
            emit gameOver ();
        }
        else
          QTimer::singleShot (1000, this, SLOT (start ()));
      }
    }
  }

  if (_mysteryShip->isVisible ()) {
    _mysteryShip->move (_mysteryShip->x () + _mysteryShipMovement, 0);
    if (_mysteryShip->x () > width ()
        || _mysteryShip->x () < -_mysteryShip->width ())
      _mysteryShip->setVisible (false);
  }
}

void InvaderFrame::armyFrameChanged ()
{
  _army->move (_army->x () + _armyMovement, _army->y ());

  if (_army->x () + _army->getMaxX () >= width ()
      || _army->x () + _army->getMinX () < 0) {
    _armyMovement *= -1;
    _army->move (_army->x () + _armyMovement, _army->y () + 10);

    if (_army->y () + _army->getMaxY () > 270) {
      stop ();
      if (_demoMode)
        emit endDemoMode ();
      else
        emit gameOver ();
    }
  }
}

void InvaderFrame::armyInvaderKilled (int score)
{
  _tankBullet->setVisible (false);

  if (_army->getInvadersAlive () == 0) {
    if (_army->y () + _army->height () + 20 < 260)
      _armyHeight += 20;
    _army->move (0, _armyHeight);
    _armyMovement = 3;
    _army->reset ();
  }

  _score += score;
  emit scoreChanged (_score);
}

void InvaderFrame::armyShoots (QPoint location)
{
  InvaderBullet *b = 0;

  switch (qrand () % 2) {
    case 0:
      b = new InvaderBullet (InvaderBullet::BulletTypeOne, this);
      break;

    default:
      b = new InvaderBullet (InvaderBullet::BulletTypeTwo, this);
      break;
  }

  _invaderBullets.push_back (b);
  QSize size = b->sizeHint ();
  b->setGeometry (location.x () + _army->x (), location.y () + _army->y (),
                  size.width (), size.height ());
  b->setVisible (true);
}

void InvaderFrame::pauseBlinkTimerTimeout ()
{
  _pauseLabel->setVisible (_pauseLabel->isHidden ());
}

void InvaderFrame::resizeEvent (QResizeEvent *event)
{
  QSize size = _tank->sizeHint ();
  _desiredTankPosition.setX (width () / 2);
  _desiredTankPosition.setY (height () - _tank->height ());
  _tank->setGeometry (width () / 2, height () - _tank->height (),
                      size.width (), size.height ());

  size = _tankBullet->sizeHint ();
  _tankBullet->setGeometry (0, 0, size.width (), size.height ());

  size = _army->sizeHint ();
  _army->setGeometry (0, _armyHeight, size.width (), size.height ());

  size = _pauseLabel->sizeHint ();
  _pauseLabel->setGeometry ((width () - size.width ()) / 2,
                            (height () - size.height ()) / 2,
                            size.width (), size.height ());

  size = _mysteryShip->sizeHint ();
  _mysteryShip->setGeometry (0, 0, size.width (), size.height ());
}

void InvaderFrame::keyPressEvent (QKeyEvent *event)
{
  if (!_demoMode) {
    switch (event->key ()) {
      case Qt::Key_Left:
        _moveTankDPos = -4;
        break;

      case Qt::Key_Right:
        _moveTankDPos = 4;
        break;

      case Qt::Key_Space:
        _fireBullet = true;

      default:
        break;
    }
  }
  else
    emit endDemoMode ();
}

void InvaderFrame::keyReleaseEvent (QKeyEvent *event)
{
  switch (event->key ()) {
    case Qt::Key_Left:
    case Qt::Key_Right:
      _moveTankDPos = 0;
      break;

    case Qt::Key_Space:
      _fireBullet = false;
      break;

    case Qt::Key_P:
      pause ();
      break;

    case Qt::Key_Escape:
      emit displayMenu ();
      break;

    default:
      break;
  }
}

void InvaderFrame::mouseMoveEvent (QMouseEvent *event)
{
  if (!_demoMode)
    _desiredTankPosition.setX (event->pos ().x ());
}

void InvaderFrame::mousePressEvent (QMouseEvent *event)
{
  if (!_demoMode && event->button () == Qt::LeftButton)
    _fireBullet = true;
}

void InvaderFrame::mouseReleaseEvent (QMouseEvent *event)
{
  if (event->button () == Qt::LeftButton)
    _fireBullet = false;
}

void InvaderFrame::removeInvaderBullet (
  std::list<InvaderBullet *>::iterator &bullet)
{
  (*bullet)->deleteLater ();
  _invaderBullets.erase (bullet);
  --bullet;
}
